import gradio as gr
import hashlib
import requests
import time
import uuid # For unique order IDs
from urllib.parse import parse_qs, urlencode # For parsing query strings and building them

# --- ZPay API Functions (Copied from your provided code) ---
# 发起支付请求
def pay(money, name, notify_url, out_trade_no, payType, pid, return_url, webName, key):
    # 对参数进行排序，生成待签名字符串
    # Note: ZPay docs might specify a different order or parameter name like 'sitename' vs 'webName'.
    # The provided example uses 'sitename' in the sg string, but the function arg is webName.
    # Let's assume 'sitename' is correct for the signature string.
    params = {
        'money': money,
        'name': name,
        'notify_url': notify_url,
        'out_trade_no': out_trade_no,
        'pid': pid,
        'return_url': return_url,
        'sitename': webName, # Assuming 'sitename' is the correct parameter name for the URL
        'type': payType
    }
    # Sort by key alphabetically for consistent signature generation (common practice)
    sorted_params = sorted(params.items())
    
    sg_parts = []
    for k, v in sorted_params:
        if v: # Only include parameters with values
            sg_parts.append(f"{k}={v}")
    
    sg_core = '&'.join(sg_parts)
    
    # MD5加密--进行签名
    sign_string = sg_core + key
    sign = hashlib.md5(sign_string.encode(encoding='UTF-8')).hexdigest()
    
    # Construct the final URL
    # Use urlencode for proper query string formatting
    query_string = sg_core + '&sign=' + sign + '&sign_type=MD5'
    url = 'https://z-pay.cn/submit.php?' + query_string
    
    print(f"Debug: ZPay Request URL (before actual request, sensitive parts like key removed from log usually): {url.replace(key, '***KEY***')}")

    try:
        # Z-Pay's submit.php often returns HTML content (e.g., a form or redirect)
        # It's not a typical JSON API endpoint for this call.
        # We should GET the URL in the browser, or if it's a POST, we need to know what data to POST.
        # The provided code does requests.post(url).content.decode()
        # This implies the parameters are already in the URL, so it should be a GET, or POST with empty body.
        # Let's try GET first as it's more common for such "submit" pages.
        # If ZPay truly expects a POST to this fully parameterized URL, then requests.post(url) is correct.
        # The original code uses requests.post(url).content.decode(). Let's stick to that.
        # However, it's unusual to POST to a URL that already contains all data as GET parameters.
        # A more standard approach would be to POST the parameters in the body.
        # Given the ZPay example, they might be doing something non-standard.
        
        response = requests.post(url) # Or requests.get(url) if that's how ZPay expects it
        response.raise_for_status() # Raise an exception for HTTP errors
        return response.content.decode()
    except requests.exceptions.RequestException as e:
        print(f"Error during ZPay pay request: {e}")
        return f"<p>Error connecting to ZPay: {e}. Please check console for URL.</p>"
    except Exception as e:
        print(f"Generic error during ZPay pay: {e}")
        return f"<p>An unexpected error occurred: {e}</p>"


# 查询商户信息与结算规则
def act(pid, key):
    url = 'https://z-pay.cn/api.php?act=query&pid=' + pid + '&key=' + key
    res = requests.get(url).content.decode()
    return res

# 修改结算账号
def change(pid, key, account, username):
    url = 'https://z-pay.cn/api.php?act=change&pid=' + pid + '&key=' + key + '&account=' + account + '&username=' + username
    res = requests.get(url).content.decode()
    return res

# 查询结算记录
def settle(pid, key):
    url = 'https://z-pay.cn/api.php?act=settle&pid=' + pid + '&key=' + key
    res = requests.get(url).content.decode()
    return res

# 查询单个订单
def order(pid, key, out_trade_no):
    url = 'https://z-pay.cn/api.php?act=order&pid='+pid+'&key='+key+'&out_trade_no='+out_trade_no
    print(f"Debug: ZPay Order Query URL: {url.replace(key, '***KEY***')}")
    try:
        response = requests.get(url)
        response.raise_for_status()
        return response.content.decode()
    except requests.exceptions.RequestException as e:
        print(f"Error during ZPay order query request: {e}")
        return f"error_request: {e}" # So we can parse this in the handler
    except Exception as e:
        print(f"Generic error during ZPay order query: {e}")
        return f"error_generic: {e}"


# 批量查询订单
def orders(pid, key, limit):
    # The original code doesn't use 'limit' in the URL string for 'orders'
    # url = 'https://z-pay.cn/api.php?act=orders&pid=' + pid + '&key=' + key
    # If limit is a parameter, it should be:
    url = f'https://z-pay.cn/api.php?act=orders&pid={pid}&key={key}&limit={limit}'
    res = requests.get(url).content.decode()
    return res
# --- END ZPay API Functions ---


# --- Gradio Interface Functions ---
def process_payment_initiation(pid_input, api_key_input, amount_input, product_name_input, payment_type_dropdown, site_name_input):
    if not all([pid_input, api_key_input, amount_input, product_name_input, payment_type_dropdown]):
        return "Error: PID, API Key, Amount, Product Name, and Payment Type are required.", None, "", "", "", ""

    try:
        money_str = "{:.2f}".format(float(amount_input)) # Ensure two decimal places
    except ValueError:
        return "Error: Invalid amount. Please enter a number (e.g., 0.01).", None, "", "", "", ""

    out_trade_no = str(uuid.uuid4()) # Generate a unique order ID
    
    # For this demo, notify_url and return_url are placeholders.
    # In a real application, these would be actual endpoints on your server.
    # For Gradio, we rely on manual status checking.
    demo_notify_url = "https://example.com/notify" # ZPay will POST here
    demo_return_url = "https://example.com/return" # User's browser will be redirected here

    web_name = site_name_input if site_name_input else "Gradio ZPay Demo"

    print(f"Initiating payment: PID={pid_input}, Amount={money_str}, Product={product_name_input}, Type={payment_type_dropdown}, OrderID={out_trade_no}")

    payment_html_content = pay(
        money=money_str,
        name=product_name_input,
        notify_url=demo_notify_url,
        out_trade_no=out_trade_no,
        payType=payment_type_dropdown,
        pid=pid_input,
        return_url=demo_return_url,
        webName=web_name,
        key=api_key_input
    )
    
    status_message = f"Payment initiated. Order ID: {out_trade_no}.\nPlease interact with the content below or any new window/tab that ZPay opens to complete payment."
    
    # Return values to update Gradio components:
    # 1. Status message
    # 2. HTML content for the payment gateway interaction
    # 3. PID (to store in state for status check)
    # 4. API Key (to store in state for status check)
    # 5. Order ID (to store in state for status check)
    # 6. Amount (to store in state for displaying upon success)
    return status_message, payment_html_content, pid_input, api_key_input, out_trade_no, money_str

def check_payment_status_interface(current_pid, current_api_key, current_out_trade_no, current_money_expected_str):
    if not all([current_pid, current_api_key, current_out_trade_no]):
        return "Error: PID, API Key, or Order ID not available. Please initiate a payment first.", ""

    print(f"Checking status for Order ID: {current_out_trade_no} with PID: {current_pid}")
    
    try:
        response_str = order(current_pid, current_api_key, current_out_trade_no)
        
        if response_str.startswith("error_request:") or response_str.startswith("error_generic:"):
             return f"API Error when checking status: {response_str}", response_str

        # Parse the ZPay response string (e.g., "code=1&msg=查询成功&trade_no=...&money=0.01&status=1")
        params = {}
        if response_str:
            try:
                params = parse_qs(response_str)
            except Exception as e:
                return f"Error parsing ZPay response: {e}\nRaw Response: {response_str}", response_str


        # Helper to safely get first element from parsed_qs list, which are lists by default
        def get_param(key, default=None):
            return params.get(key, [default])[0]

        code = get_param('code')
        msg = get_param('msg', 'No message from API')
        api_money_paid = get_param('money')
        api_status = get_param('status') # ZPay specific status for successful payment (often '1')
        api_trade_status = get_param('trade_status') # e.g., TRADE_SUCCESS

        result_message = f"Order ID: {current_out_trade_no}\n"
        result_message += f"API Message: {msg}\n"
        result_message += f"Raw API Code: {code}, API Status: {api_status}, API Trade Status: {api_trade_status}\n"

        # According to common patterns and some ZPay docs:
        # - code=1 often means the API call itself was successful (order found).
        # - status=1 within the data often means the order is paid.
        # - trade_status='TRADE_SUCCESS' is also a strong indicator.
        if code == '1' and api_status == '1': # Check if ZPay considers '1' as paid
            if api_money_paid == current_money_expected_str:
                result_message += f"\n🎉 PAYMENT CONFIRMED! 🎉\nPaid: {api_money_paid} CNY"
            else:
                result_message += f"\n⚠️ PAYMENT CONFIRMED, BUT AMOUNT MISMATCH! ⚠️\nExpected: {current_money_expected_str}, Paid: {api_money_paid} CNY"
        elif code == '1' and api_status == '0': # Order found, but not paid
             result_message += f"\n⏳ PAYMENT PENDING or NOT COMPLETED.\n(API status: {api_status})"
        elif code == '0': # Order not found or other error reported by 'code'
            result_message += f"\n❌ PAYMENT NOT FOUND or FAILED.\n(API code: {code})"
        else: # Other cases
            result_message += f"\n❓ UNCERTAIN PAYMENT STATUS. Please verify manually.\n(API code: {code}, API status: {api_status})"

        return result_message, response_str

    except requests.exceptions.RequestException as e:
        return f"Network Error connecting to ZPay API for status check: {str(e)}", ""
    except Exception as e:
        return f"An error occurred while checking payment status: {str(e)}", ""

# --- Gradio UI ---
with gr.Blocks(theme=gr.themes.Soft(), title="ZPay Gradio Demo") as demo:
    gr.Markdown("# ZPay Payment Integration with Gradio")
    gr.Markdown(
        """
        **Disclaimer:** This is a demonstration. Ensure your PID and Key are correct.
        For real transactions, always verify payment status thoroughly through multiple means if possible.
        The `notify_url` and `return_url` are placeholders in this demo.
        A real application would require proper server endpoints for these.
        """
    )

    # Store necessary info between steps using gr.State
    # These will be populated after payment initiation and used for status check
    current_pid_state = gr.State("")
    current_api_key_state = gr.State("")
    current_out_trade_no_state = gr.State("")
    current_money_expected_state = gr.State("") # Store the amount expected

    with gr.Tabs():
        with gr.TabItem("💳 Initiate Payment"):
            with gr.Row():
                with gr.Column(scale=1):
                    gr.Markdown("## 1. Enter Payment Details")
                    pid_input = gr.Textbox(label="Your ZPay PID (商户ID)", placeholder="e.g., 1234")
                    api_key_input = gr.Textbox(label="Your ZPay API Key (密钥)", type="password", placeholder="e.g., yoursecretkey")
                    amount_input = gr.Textbox(label="Amount (e.g., 0.01)", placeholder="0.01")
                    product_name_input = gr.Textbox(label="Product Name", value="Test Product XYZ")
                    payment_type_dropdown = gr.Dropdown(
                        label="Payment Type",
                        choices=[
                            ("Alipay (支付宝)", "alipay"),
                            ("WeChat Pay (微信支付)", "wxpay"),
                            ("QQ Wallet (QQ钱包)", "qqpay"),
                            ("Tenpay (财付通)", "tenpay") # Included as per original code comments
                        ],
                        value="alipay" # Default selection
                    )
                    site_name_input = gr.Textbox(label="Website Name (Optional)", placeholder="My Awesome Shop")
                    
                    initiate_button = gr.Button("🚀 Initiate Payment", variant="primary")

                with gr.Column(scale=2):
                    gr.Markdown("## 2. ZPay Interaction")
                    status_message_output = gr.Textbox(label="Initiation Status", interactive=False, lines=3)
                    payment_gateway_html = gr.HTML(label="ZPay Payment Window/Form")
                    gr.Markdown(
                        """
                        **Action Required:**
                        After clicking "Initiate Payment", content from ZPay will load above.
                        This might be a QR code, a form, or a redirect.
                        - **You MUST interact with it** (e.g., scan QR, click submit, or allow redirect) to make the payment.
                        - Your browser's pop-up blocker might interfere if ZPay tries to open a new window.
                        - Once you believe payment is made, go to the "Check Payment Status" tab or click the button below.
                        """
                    )
        
        with gr.TabItem("🔍 Check Payment Status"):
            gr.Markdown("## 3. Check Payment Status")
            gr.Markdown(
                """
                After attempting payment via the ZPay interface (in the previous tab or a new window ZPay opened),
                use this section to verify if ZPay has marked the transaction as complete.
                The Order ID from the 'Initiate Payment' step will be used automatically.
                """
            )
            check_status_button = gr.Button("🔄 Check Current Order Status", variant="primary")
            payment_status_output = gr.Textbox(label="Final Payment Status", interactive=False, lines=7)
            raw_api_response_output = gr.Textbox(label="Raw API Response (Order Query)", interactive=False, lines=3, placeholder="Full response from ZPay API will appear here...")


    # --- Event Handlers ---
    initiate_button.click(
        fn=process_payment_initiation,
        inputs=[
            pid_input, api_key_input, amount_input, product_name_input,
            payment_type_dropdown, site_name_input
        ],
        outputs=[
            status_message_output, payment_gateway_html,
            current_pid_state, current_api_key_state, # Store these for checking status
            current_out_trade_no_state, current_money_expected_state
        ]
    )

    check_status_button.click(
        fn=check_payment_status_interface,
        inputs=[current_pid_state, current_api_key_state, current_out_trade_no_state, current_money_expected_state],
        outputs=[payment_status_output, raw_api_response_output]
    )

    gr.Markdown(
        """
        ---
        **Notes on ZPay `pay` function parameters from your example:**
        - `money`: 金额
        - `name`: 商品名称
        - `notify_url`: 服务器异步通知地址 (Critical for production, placeholder in demo)
        - `out_trade_no`: 商户订单号 (Generated automatically in this demo)
        - `payType`: 支付方式 (alipay, wxpay, qqpay, tenpay)
        - `pid`: 商户ID
        - `return_url`: 页面跳转通知地址 (Placeholder in demo)
        - `webName` (or `sitename`): 网站名称
        - `key`: 密钥
        The signature `sg` string construction in the original `pay` function needed adjustment to sort parameters for robust signing, which has been done.
        The exact parameter name for 'website name' in the signature string can sometimes be `sitename` or `webName`. The provided example code used `sitename` in `sg` string, which is now reflected.
        """
    )

if __name__ == "__main__":
    print("Starting ZPay Gradio Demo...")
    # For testing, you might want to pre-fill PID and Key if you have test credentials.
    # Otherwise, users will input them in the UI.
    # Example for local testing only:
    # pid_input.value = "YOUR_TEST_PID"
    # api_key_input.value = "YOUR_TEST_KEY"
    
    demo.launch()
    # To make it accessible on your local network: demo.launch(server_name="0.0.0.0")
    # To share temporarily via Gradio's relay: demo.launch(share=True) (use with caution, especially with API keys)